from collections import deque

from classact.transport import Message, Response
from classact.log import log_funcall, actor_logger, NullFile

# Define several message formats for log entries.
msg_received_fmt = 'MSG(%s) || RECEIVED || %s'
msg_sent_fmt = 'MSG(%s) || SENT || %s'
response_received_fmt = 'RESP(%s) || RECEIVED || %r'
response_sent_fmt = 'RESP(%s) || SENT || %r'
response_received_error_fmt = 'RESP(%s) || RECEIVED || %s: %s'
response_sent_error_fmt = 'RESP(%s) || SENT || %s: %s'


class MemoProc(dict):

    """
    A class which acts as a decorator to create memoized functions.
    
    This class, which may be used as a decorator, provides a means of memoizing
    functions. See `Wikipedia <http://en.wikipedia.org/wiki/Memoization>`_ for
    more information on memoization. Essentially, memoization is a form of
    caching which is useful for any function which is `referentially
    transparent  <http://en.wikipedia.org/wiki/\
    Referential_transparency_%28computer_science%29>`_; that is, any operation
    for which successive function calls return the same value, and which
    produces no side effects.
    
    There are two very good examples of where this is useful; a recursive
    factorial definition (so that successive recursions are less expensive),
    and a recursive definition for the fibonacci numbers (so that
    already-calculated values can be obtained much more quickly).
    
    This is also used in the ``Actor.lookup_actor`` method definition, as actor
    lookups are operations which benefit greatly from caching.
    
    This particular implementation does not expunge old results from the cache;
    a MemoProc instance therefore holds on to its cache until one of the
    following events occur:
        
        * A procedure calls ``MemoProc.clear()``, or otherwise manually resets
        the cache to an empty dictionary.
        
        * The MemoProc instance is deleted, through an explicit ``del`` or
        garbage collection.

    A ``MemoProc`` instance has one main attribute, ``function``, which is the
    function to be memoized.
    
    ``MemoProc`` also defines several methods:
        
        ``__init__``
            Extend ``dict.__init__`` by accepting a function and setting the
            ``function`` attribute to be equal to that function. Then, call
            ``dict.__init__`` on the ``MemoProc`` instance to initialize
            dictionary-like behaviour, passing along all unhandled positional
            and keyword arguments to ``dict.__init__``.
        
        ``__call__``
            This is the implementation of memoization; it allows the MemoProc
            instance to be called as a regular function, and thus facilitates
            its ability to be used as a decorator. See its individual docstring
            for more information, and an important warning as to
        
        ``__repr__``
            By default, calling ``MemoProc.__repr__`` would return the string
            representation of the dictionary it represents. This overrides it,
            providing a nicer looking representation which displays it in a
            similar way to normal Python functions.
            In this case, a memoized function ``fibonacci`` might be shown as
            ``<memoized_function fibonacci at 0xb76d4b1c>``.
    
    """

    def __init__(self, function, *args, **kwargs):
        """
        Initialize a memoized function.
        
        This method takes a function and returns a ``MemoProc`` instance. Any
        additional positional and keyword arguments are passed to
        ``dict.__init__``. This is useful if you want to pre-cache some
        function calls. For example::
            
            def factorial(n):
                return n * factorial(n-1)
            factorial = MemoProc(factorial, **{0: 1})
        
        In this example, it is not necessary to define the case for
        ``factorial(0)``, as the result of such a computation is pre-cached.
        
        """
        self.function = function
        dict.__init__(self, *args, **kwargs)

    def __call__(self, *args):
        """
        Call a memoized function, returning the cached output of the function.
        
        The ``__call__`` method allows a ``MemoProc`` instance to be called as
        a regular function, meaning ``MemoProc`` may be used as a function
        decorator.
        
        **Note**: in this particular implementation, mutable arguments are not
        supported, as all positional arguments are stored in a tuple which is
        then used as a key in the dictionary. Dictionaries, in Python, do not
        support mutable data in keys, and so keyword arguments cannot be used
        either (as they facilitate storage in a dictionary, which is mutable).
        
        An alternative implementation could be to pickle arguments and keyword
        arguments and use this pickle string as a key, but for this usage case
        (as a caching wrapper for ``Actor.lookup_actor``) such a provision is
        not necessary and would incur a slight performance lag, hence it is not
        included.
        
        """
        # The tuple ``args``, which contains the arguments, is used as a key in
        # the dictionary (the ``MemoProc`` instance itself).
        if not args in self:
            self[args] = self.function(*args)
        return self[args]
    
    def __repr__(self):
        """Provide a string representation of a memoized function."""
        # Output looks like ``<memoized_function lookup_actor at 0xb76d4b1c>``
        return '<memoized_function %s at 0x%x>' % (
            self.function.__name__,
            id(self))


def actor_method(function):
    """
    Add an ``actor_method`` attribute to a given function, and return it.
    
    This is to be used as a decorator for methods when subclassing ``Actor``.
    Methods on an actor which are intended to be used by other actors are
    decorated by this function, which attaches to them an ``actor_method``
    attribute and assigns it a value of ``True``. The modified function is
    returned, in order to comply with the decorator behavior.
    
    """
    function.actor_method = True
    return function


class MsgDispatchHandler(threading.Thread):
    
    """
    Perform a dispatch on a given message and actor.
    
    The ``MsgDispatchHandler`` class is spawned when an actor instance receives
    a message. It takes the actor and the message, and, when started, performs
    a dispatch on the message, enabling the actor instance to act as an RPC
    server.
    """
    
    def __init__(self, actor, msg, *args, **kwargs):
        threading.Thread.__init__(self, *args, **kwargs)
        self.actor = actor
        self.msg = msg
    
    def run(self):
        # Log the reception of the message.
        self.actor.log.info(msg_received_fmt, self.msg.id,
            log_funcall(self.msg.message, self.msg.arguments,
                self.msg.keyword_arguments))
        # Initialize response, to be populated with data shortly.
        response = Response(self.msg, None)
        # The dispatch is performed within a try-except clause because
        # it allows exceptions to be caught and returned to the caller.
        try:
            ret_val = self.actor.dispatch_by_message(self.msg)
        # ``BaseException`` is used here because it is a parent of all
        # stdlib exceptions, and should be of all others, too. Also, an
        # exception class must be specified in order to catch the
        # exception instance.
        except BaseException, exc_instance:
            response.exception = exc_instance
        else:
            response.value = ret_val
            self.actor.controller.register_response(response)
        # Similar to what is done when a response with an exception is
        # received, when the actor sends out a response with an
        # exception, it is logged as an error or warning. See method
        # ``Actor.send`` for the reverse (logging an exception received
        # in a response).
        if isinstance(response.exception, Exception):
            self.actor.log.error(response_sent_error_fmt, response.id,
                response.exception.__class__.__name__,
                response.exception.message)
        elif isinstance(response.exception, Warning):
            self.actor.log.warning(response_sent_error_fmt, response.id,
                response.exception.__class__.__name__,
                response.exception.message)
        # If there was no exception, log it at the 'DEBUG' level.
        else:
            self.actor.log.debug(response_sent_fmt, response.id,
                response.value)
        return


class Actor(threading.Thread):
    
    """
    The parent ``Actor`` class, from which all actors should be subclassed.
    
    The ``Actor`` class is a subclass of ``threading.Thread``, meaning
    individual actors are run as threads. There are several attributes and
    methods defined for actors. The standard attributes are as follows:
    
        ``id``
            This is a Universally Unique Identifier (UUID), generated by the
            actor's controller. Due to the enormous improbability of a
            collision in UUIDs, they are used to identify actors uniquely, both
            on single controllers and across networks of controllers.
        
        ``controller``
            This is a reference to the ``Controller`` instance which controls
            the actor. It should be a raw controller, not a ``ControllerProxy``
            instance. This is to make sure that an actor is controlled by a
            controller which is locally available, in order to reduce lag, etc.
        
        ``msg_queue``
            This is a ``deque`` instance ('deque' means double-ended queue),
            from the built-in ``collections`` module (new in version 2.4),
            which is used by the actor's main loop to receive messages.
        
        ``response_queue``
            This is also a ``deque`` instance which is used by the ``send``
            method to receive responses to messages sent.
        
        ``log``
            The ``log`` attribute points to a logger instance, as created by
            the ``classact.log.actor_logger`` function. It writes its output
            to a stream, which could be a file, socket or file-like object. See
            the documentation on the ``__init__`` method for more information
            on how to specify this stream.
        
    The class also exports several basic methods:
        
        ``__init__``
            This initializes a new actor, given a controller and (optionally) a
            stream to be used as a log file. It carries out all necessary
            population of attributes, and registers the actor with the given
            controller. It also logs the initialization of the actor, at the
            'INFO' level, on the actor log *and* the controller log.
            As with all subclasses of ``threading.Thread``, the ``__init__``
            method of ``threading.Thread`` is called on the newly-created
            instance after all initializations have been done.
        
        ``lookup_actor``
            This method, when supplied with an actor ID, returns either:
                * An ``ActorProxy`` instance, which points to an actor hosted
                either locally (in this Python interpreter process), on a
                loopback (on this machine, but another process), remotely (on
                another machine) or remotely *and* securely (i.e. over an SSL-
                or TLS-encrypted connection). See the modules
                ``classact.local``, ``classact.loopback``, ``classact.remote``
                and ``classact.remote_secure`` for more information.
                * ``None``, when the actor ID is not found.
            See the ``lookup_actor`` docstring for more information.
        
        ``send``
            This method accepts a recipient actor ID, message, an iterable of
            positional arguments and a dictionary of keyword arguments, and
            from this constructs a ``Message`` instance, sends it to the
            recipient and waits for a response. If an exception is received,
            then it is raised. Otherwise, the response value is returned.
        
        ``dispatch_by_message``
            This function, used by the actor when a message is received,
            matches the message's desired function name up with a defined
            method on the actor instance. The method, if found, is called with
            the provided arguments and keyword arguments, and the value of the
            call returned. Otherwise, an exception is raised.
        
        ``run``
            This method overrides the default, defined in ``threading.Thread``,
            and contains the definition for the actor loop. See the individual
            docstring for ``Actor.run`` for more information on this loop.
        
    In order to start the actor, call the ``start`` method on it with no
    arguments, in the same way as you would start a thread.
    
    """
    
    def __init__(self, controller, logfile=NullFile(), *args, **kwargs):
        """
        Initialize an ``Actor`` instance on a particular controller.
        
        This method accepts a controller as a positional argument, and supports
        the keyword argument ``logfile``, which should be a file-like object to
        which the log output of this actor will be written. When the actor is
        killed this log file will be closed.
        
        """
        # The following is required for all ``threading.Thread`` subclasses
        # which extend the ``__init__`` method.
        threading.Thread.__init__(self, *args, **kwargs)
        # Thread termination flag.
        self.controller = controller
        # ``Controller.register_actor`` returns the newly created actor id.
        self.id = controller.register_actor(self)
        self.msg_queue = deque()
        self.response_queue = deque()
        self.log = actor_logger(logfile)
        # Log initialization on both actor and controller logs.
        self.log.info('ACTR(%s) || Actor initialized', self.id)
        self.controller.log.info('ACTR(%s) || Actor initialized', self.id)

    @MemoProc
    def lookup_actor(self, actor_id):
        return self.controller.lookup_actor(self, actor_id)

    def send(self, rcpt, msg, args=[], kwargs={}):
        # First, it's necessary to create the ``Message`` instance. By doing
        # so, it allows the process of message sending/receiving to be
        # transparent of the medium through which messages are sent.
        message = Message(self, rcpt, msg, args=args, kwargs=kwargs)
        self.controller.register_msg(message)
        # Log the sending of the message in the actor log. The controller's
        # message handler will also log the sending of the message. This allows
        # the time taken for transfers to be measured and profiled.
        self.log.info(msg_sent_fmt, message.id,
            log_funcall(msg, args, kwargs))
        # Begin loop which checks for the appropriate response from the
        # recipient actor.
        while True:
            # Begin search loop, which goes through each response in the queue
            # and checking the id of the sent message against the id of the
            # original message, which is held inside the response.
            for i in xrange(len(self.response_queue)):
                response = self.response_queue[i]
                if response.orig_msg.id == message.id:
                    # If a match is found, remove that match from the queue.
                    self.response_queue.pop(i)
                    # If an exception was returned, log it locally and then
                    # raise the exception instance.
                    if isinstance(response.exception, Exception):
                        self.log.error(response_received_error_fmt,
                            response.id,
                            response.exception.__class__.__name__,
                            response.exception.message)
                        raise response.exception
                    # Do the same for a warning, only log it at a lower level.
                    elif isinstance(response.exception, Warning):
                        self.log.warning(response_received_error_fmt,
                            response.id,
                            response.exception.__class__.__name__,
                            response.exception.message)
                        raise response.exception
                    else:
                        self.log.info(response_received_fmt, response.id,
                            response.value)
                        return response.value

    def dispatch_by_message(self, msg):
        if hasattr(self, msg.message):
            function = self.__getattribute__(msg.message)
            if hasattr(function, 'actor_method'):
                if function.actor_method:
                    return function(msg.sender,
                        *msg.arguments, **msg.keyword_arguments)
        raise AttributeError('Method "%s" not found' % msg.message)

    def run(self):
        # Log startup on actor and controller logs.
        self.log.info('ACTR(%s) || Actor started', self.id)
        self.controller.log.info('ACTR(%s) || Actor started', self.id)
        # Begin main thread loop which pops messages of message queue and
        # spawns a dispatch handler thread to handle each message.
        while True:
            if len(self.msg_queue) > 0:
                MsgDispatchHandler(self, self.msg_queue.pop()).start()